package com.vip.oauth2.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vip.common.constant.PublicConsts;
import com.vip.common.response.ErrorCode;
import com.vip.common.response.Result;
import com.vip.common.response.Results;
import com.vip.common.util.MD5Utils;
import com.vip.common.util.MyBeanUtils;
import com.vip.database.dto.user.GetUserDTO;
import com.vip.database.entity.SysUser;
import com.vip.database.mapper.SysUserMapper;
import com.vip.oauth2.config.ServerInitListenerImpl;
import com.vip.oauth2.constant.AuthConsts;
import com.vip.oauth2.dto.token.LocalToken;
import com.vip.oauth2.service.ISysUserService;
import com.vip.oauth2resource.util.CustomUserDetail;
import com.vip.redis.component.RedisOperate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.TimeUnit;

/**
 * 用户Service
 *
 * @author wgb
 * @date 2020/3/20 16:18
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    private final SysUserMapper userMapper;
    private final RestTemplate restTemplate;
    private final ServerInitListenerImpl serverInitListenerImpl;
    private final RedisOperate redisOperate;

    @Override
    public Result<CustomUserDetail> userLogin(String username, String password) {
        try {
            MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
            paramMap.add("client_id", AuthConsts.CLIENT_ID);
            paramMap.add("client_secret", AuthConsts.CLIENT_SECRET);
            paramMap.add("username", username);
            paramMap.add("password", MD5Utils.md5(password, PublicConsts.SALT));
            paramMap.add("grant_type", AuthConsts.GRANT_TYPE[0]);
            LocalToken tokenInfo;
            try {
                // 因为oauth2本身自带的登录接口是"/oauth/token"，并且返回的数据类型不能按我们想要的去返回
                // 但是我的业务需求是，登录接口是"/user/login"，由于我没研究过要怎么去修改oauth2内部的endpoint配置
                // 所以这里我用restTemplate(HTTP客户端)进行一次转发到oauth2内部的登录接口，比较简单粗暴
                tokenInfo = restTemplate.postForObject(serverInitListenerImpl.getUrl() + AuthConsts.LOGIN_URL, paramMap, LocalToken.class);
                CustomUserDetail loginUserVO = redisOperate.get(tokenInfo.getValue(), CustomUserDetail.class);
                // redis里面已经存在该用户已经登录过了的信息，刷新一遍token信息，不然，它会返回上一次还未过时的token信息给你，不便于做单用户维护
                // TODO 单用户登录
                if (loginUserVO != null) {
                    tokenInfo = oauthRefreshToken(loginUserVO.getRefreshToken());
                    redisOperate.deleteCache(loginUserVO.getAccessToken());
                }
            } catch (RestClientException e) {
                return Results.newFailResult(ErrorCode.LOGIN_EXCEPTION, "账号或密码错误");
            }
            //这里我拿到了登录成功后返回的token信息之后，我再进行一层封装，最后返回给前端的其实是LoginUserDto
            CustomUserDetail userInfo = new CustomUserDetail();
            GetUserDTO user = userMapper.findUserBaseInfoByAccount(username);
            MyBeanUtils.copyPropertiesIgnoreNull(user, userInfo);
            userInfo.setAccessToken(tokenInfo.getValue());
            userInfo.setAccessTokenExpiresIn(tokenInfo.getExpiresIn());
            userInfo.setAccessTokenExpiration(tokenInfo.getExpiration());
            userInfo.setExpired(tokenInfo.isExpired());
            userInfo.setScope(tokenInfo.getScope());
            userInfo.setTokenType(tokenInfo.getTokenType());
            userInfo.setRefreshToken(tokenInfo.getRefreshToken().getValue());
            userInfo.setRefreshTokenExpiration(tokenInfo.getRefreshToken().getExpiration());
            //存储登录的用户
            redisOperate.set(userInfo.getAccessToken(), userInfo, TimeUnit.HOURS.toSeconds(1));
            return Results.newSuccessResult(userInfo, "登陆成功");
        } catch (Exception e) {
            log.error("登陆过程出错", e);
        }
        return Results.newFailResult(ErrorCode.LOGIN_EXCEPTION, "登陆失败");
    }

    /**
     * oauth2客户端刷新token
     *
     * @param refreshToken
     * @return
     */
    private LocalToken oauthRefreshToken(String refreshToken) {
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
        paramMap.add("client_id", AuthConsts.CLIENT_ID);
        paramMap.add("client_secret", AuthConsts.CLIENT_SECRET);
        paramMap.add("refresh_token", refreshToken);
        paramMap.add("grant_type", AuthConsts.GRANT_TYPE[1]);
        LocalToken token = null;
        try {
            token = restTemplate.postForObject(serverInitListenerImpl.getUrl() + AuthConsts.LOGIN_URL, paramMap, LocalToken.class);
        } catch (RestClientException e) {
            try {
                throw new Exception(ErrorCode.REFRESH_TOKEN_INVALID.getDescription());
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
        return token;
    }

}
